using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;

namespace Charisma.Conventions
{
    public class NamedReferenceMemberConvention : IReferenceMemberConvention
    {
        private const string KeyPropertyName = "id";
        private static readonly StringComparer comparer = StringComparer.OrdinalIgnoreCase;
        private static readonly List<string> backingFieldNamePatterns;

        static NamedReferenceMemberConvention()
        {
            backingFieldNamePatterns = new List<string> {"{0}{1}", "{0}_{1}"};
        }

        #region IReferenceMemberConvention Members

        public IEnumerable<MemberPair> Find(Type type, IEnumerable<MemberInfo> fields)
        {
            // Trying to find properties of non-primitive types
            List<MemberInfo> references = fields
                .Where(mi =>
                           {
                               Type memberType = mi.GetMemberType();
                               return memberType.IsClass && memberType != typeof (string);
                           })
                .ToList();

            if (references.Count() == 0)
                return Enumerable.Empty<MemberPair>();

            // Discovering backing members
            Dictionary<string, MemberInfo> backingFields = fields
                .Except(references)
                .Where(p => p.Name.ToLower().EndsWith(KeyPropertyName))
                .ToDictionary(p => p.Name, comparer);
            if (backingFields.Count() == 0)
                return Enumerable.Empty<MemberPair>();

            return (from reference in references
                    let backingField = FindBackingField(reference.Name, backingFields)
                    where backingField != null
                    select new MemberPair(reference, backingField)).ToList();
        }

        #endregion

        private static MemberInfo FindBackingField(string referenceName, IDictionary<string, MemberInfo> backingFields)
        {
            Contract.Requires(!String.IsNullOrEmpty(referenceName));
            Contract.Requires(backingFields != null);

            foreach (string pattern in backingFieldNamePatterns)
            {
                string name = string.Format(pattern, referenceName, KeyPropertyName);
                MemberInfo result;
                if (backingFields.TryGetValue(name, out result))
                    return result;
            }
            return null;
        }
    }
}